<?php
namespace core\qiniuyun\lib;

class Header implements \ArrayAccess, \IteratorAggregate, \Countable
{
    /** @var array normalized key name map */
    private $data = array();

    /**
     * @param array $obj non-normalized header object
     */
    public function __construct($obj = array())
    {
        foreach ($obj as $key => $values) {
            $normalizedKey    = self::normalizeKey($key);
            $normalizedValues = array();
            foreach ($values as $value) {
                array_push($normalizedValues, self::normalizeValue($value));
            }
            $this->data[$normalizedKey] = $normalizedValues;
        }
        return $this;
    }

    /**
     * return origin headers, which is field name case-sensitive
     *
     * @param string $raw
     *
     * @return array
     */
    public static function parseRawText($raw)
    {
        $headers     = array();
        $headerLines = explode("\r\n", $raw);
        foreach ($headerLines as $line) {
            $headerLine = trim($line);
            $kv         = explode(':', $headerLine);
            if (count($kv) <= 1) {
                continue;
            }
            // for http2 [Pseudo-Header Fields](https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.1)
            if ($kv[0] == "") {
                $fieldName = ":" . $kv[1];
            } else {
                $fieldName = $kv[0];
            }
            $fieldValue = trim(substr($headerLine, strlen($fieldName . ":")));
            if (isset($headers[$fieldName])) {
                array_push($headers[$fieldName], $fieldValue);
            } else {
                $headers[$fieldName] = array($fieldValue);
            }
        }
        return $headers;
    }

    /**
     * @param string $raw
     *
     * @return Header
     */
    public static function fromRawText($raw)
    {
        return new Header(self::parseRawText($raw));
    }

    /**
     * @param string $key
     *
     * @return string
     */
    public static function normalizeKey($key)
    {
        $key = trim($key);

        if (!self::isValidKeyName($key)) {
            return $key;
        }

        return ucwords(strtolower($key), '-');
    }

    /**
     * @param string|numeric $value
     *
     * @return string|numeric
     */
    public static function normalizeValue($value)
    {
        if (is_numeric($value)) {
            return $value + 0;
        }
        return trim($value);
    }

    /**
     * @return array
     */
    public function getRawData()
    {
        return $this->data;
    }

    /**
     * @param $offset string
     *
     * @return boolean
     */
    #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x
    public function offsetExists($offset)
    {
        $key = self::normalizeKey($offset);
        return isset($this->data[$key]);
    }

    /**
     * @param $offset string
     *
     * @return string|null
     */
    #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x
    public function offsetGet($offset)
    {
        $key = self::normalizeKey($offset);
        if (isset($this->data[$key]) && count($this->data[$key])) {
            return $this->data[$key][0];
        } else {
            return null;
        }
    }

    /**
     * @param $offset string
     * @param $value string
     *
     * @return void
     */
    #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x
    public function offsetSet($offset, $value)
    {
        $key = self::normalizeKey($offset);
        if (isset($this->data[$key]) && count($this->data[$key]) > 0) {
            $this->data[$key][0] = self::normalizeValue($value);
        } else {
            $this->data[$key] = array(self::normalizeValue($value));
        }
    }

    /**
     * @return void
     */
    #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x
    public function offsetUnset($offset)
    {
        $key = self::normalizeKey($offset);
        unset($this->data[$key]);
    }

    /**
     * @return \ArrayIterator
     */
    #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x
    public function getIterator()
    {
        $arr = array();
        foreach ($this->data as $k => $v) {
            $arr[$k] = $v[0];
        }
        return new \ArrayIterator($arr);
    }

    /**
     * @return int
     */
    #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x
    public function count()
    {
        return count($this->data);
    }

    private static $isTokenTable = array(
        '!'  => true,
        '#'  => true,
        '$'  => true,
        '%'  => true,
        '&'  => true,
        '\'' => true,
        '*'  => true,
        '+'  => true,
        '-'  => true,
        '.'  => true,
        '0'  => true,
        '1'  => true,
        '2'  => true,
        '3'  => true,
        '4'  => true,
        '5'  => true,
        '6'  => true,
        '7'  => true,
        '8'  => true,
        '9'  => true,
        'A'  => true,
        'B'  => true,
        'C'  => true,
        'D'  => true,
        'E'  => true,
        'F'  => true,
        'G'  => true,
        'H'  => true,
        'I'  => true,
        'J'  => true,
        'K'  => true,
        'L'  => true,
        'M'  => true,
        'N'  => true,
        'O'  => true,
        'P'  => true,
        'Q'  => true,
        'R'  => true,
        'S'  => true,
        'T'  => true,
        'U'  => true,
        'W'  => true,
        'V'  => true,
        'X'  => true,
        'Y'  => true,
        'Z'  => true,
        '^'  => true,
        '_'  => true,
        '`'  => true,
        'a'  => true,
        'b'  => true,
        'c'  => true,
        'd'  => true,
        'e'  => true,
        'f'  => true,
        'g'  => true,
        'h'  => true,
        'i'  => true,
        'j'  => true,
        'k'  => true,
        'l'  => true,
        'm'  => true,
        'n'  => true,
        'o'  => true,
        'p'  => true,
        'q'  => true,
        'r'  => true,
        's'  => true,
        't'  => true,
        'u'  => true,
        'v'  => true,
        'w'  => true,
        'x'  => true,
        'y'  => true,
        'z'  => true,
        '|'  => true,
        '~'  => true,
    );

    /**
     * @param string $str
     *
     * @return boolean
     */
    private static function isValidKeyName($str)
    {
        for ($i = 0; $i < strlen($str); $i += 1) {
            if (!isset(self::$isTokenTable[$str[$i]])) {
                return false;
            }
        }
        return true;
    }
}
